/* Utility Functions */
String.prototype.CLtrim = function(){
    newString = this;

    while(newString.charAt(0) == ' '){
        newString = newString.substring(1);
    }

    while(newString.charAt(newString.length-1) == ' '){
        newString = newString.substring(0, newString.length - 1);
    }

    return newString;
};

String.prototype.CLisIP = function(){
    var result = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.exec(this);
    return result != null && result.toString() == this.toString();
};

const showIP = new function() {

    /* global variables */
    var lookUpArray = null;
    var multipart = '';
    var currentDNSRequest = null;

    const inet_aton = function(address){
        address = address.split(".");

        number = address[0]*256*256*256 + address[1]*256*256 + address[2]*256 + Number(address[3]);

        return number;
    };

    const isPrivate = function(address){
        // Is the supplied IP address part of a private network or loopback?
        // http://www.iana.org/faqs/abuse-faq.htm#SpecialUseAddresses
        var number = inet_aton(address);

        if(
        // Private use
           (167772160 <= number && number <= 184549375)
        || (2886729728 <= number && number <= 2887778303)
        || (3232235520 <= number && number <= 3232301055)
        // Loopback
        || (2130706432 <= number && number <= 2147483647)
        // I don't think the following ones are possible, but anyway
        // Autoconfiguration
        || (2851995648 <= number && number <= 2852061183)
        // Multicast
        || (3758096384 <= number && number <= 4026531839)
        ){
            return true;
        } else {
            return false;
        }
    };

    const findIPInfo = function(){
        var originIP = null;
        var receivedHeaders = new Array();
        var IPArray = new Array();
        var xOriginatingIP = null;
        var foundMultipart = false;
        var srcMsgURI = GetLoadedMessage();

        /* no message */
        if (srcMsgURI == null) {
            dump("mailRoute::dispRoute: no message\r\n");
            return;
        }
        // This fixes a bug related to multipart messages
        // I think this is a bug in EnigMail, but whatever, the fix is easy
        // START FIX
        for(i in currentHeaderData){
            if (
                 currentHeaderData[i].headerName.toLowerCase() == "content-type"
            && currentHeaderData[i].headerValue.indexOf('multipart') == 0
            ) {
                foundMultipart = true;
                if(multipart == currentHeaderData[i].headerValue){
                    return;
                } else {
                    multipart = currentHeaderData[i].headerValue;
                }
            }
        }

        if(!foundMultipart){
            multipart = '';
        }
        // END FIX

        if(currentDNSRequest){ // We don't want the DNS callback to be called while we are parsing another e-mail
            currentDNSRequest.cancel(Components.results.NS_ERROR_FAILURE);
            currentDNSRequest = null;
        }

        // Get all Received headers
        for(i in currentHeaderData){
            if (currentHeaderData[i].headerName.toLowerCase() == "received") {
                receivedHeaders[receivedHeaders.length] = currentHeaderData[i].headerValue;
            }
        }
        receivedHeaders.reverse();

        for(var i = receivedHeaders.length-1; i>=0; i--){
            var currentIPs = receivedHeaders[i].match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g);
            if(currentIPs != null){
                IPArray = IPArray.concat(currentIPs);
            }
        }

        // Look for X-Originating-IP header
        for(i in currentHeaderData){
      if (currentHeaderData[i].headerName.toLowerCase() == "x-originating-ip") {
                xOriginatingIP = currentHeaderData[i].headerValue;
                if(xOriginatingIP.charAt(0) == "["){
                    xOriginatingIP = xOriginatingIP.substring(1, xOriginatingIP.length-1);
                }
            }
        }

        // If X-Originating-IP isn't set, why bother?
        // If X-Originating-IP is also in the Received headers,
        // the Received headers know better.
        // If X-Originating-IP is private, ignore.
        // Else, it almost certainly is from Hotmail or another web
        // based e-mail service imitating them. In this case
        // X-Originating-IP is the best guess.
        if(xOriginatingIP != null && IPArray.indexOf(xOriginatingIP) == -1 && !isPrivate(xOriginatingIP)){
            originIP = xOriginatingIP;
        }

        if(originIP == null){
            // Maybe it's an NNTP message
            for (var i in currentHeaderData) {
                if (currentHeaderData[i].headerName.toLowerCase() == "nntp-posting-host") {
                    var nntpHost = currentHeaderData[i].headerValue;

          nntpHost = nntpHost.split(' ');
          for(var i = 0; i < nntpHost.length; i++){
            if(nntpHost[i].charAt(0) == "["){
                        nntpHost[i] = nntpHost[i].substring(1, nntpHost[i].length-1);
                    }

            if(nntpHost[i].CLisIP() && !isPrivate(nntpHost[i])){
              originIP = nntpHost[i];
            }
          }

          if(originIP == null){
            information.tcpDomain = nntpHost;
            information.nntp = true;

            doDNSLookups();
            return;
          }
                }
            }
        }

        // If we haven't found the origin IP yet, parse the Received headers.
        // As this requires asynchronous dns looukups, we place this in a seperate function
        // Else, continue
        if(originIP == null){
            prepareToParseReceivedHeaders(receivedHeaders);
        } else {
            dispRouteAfterDNS(originIP);
        }
    };

    const information = new function(){
        this.receivedHeaders = null;
        this.tcpDomain = null;
        this.currentTCPDomain = null;
    this.nntp = false;
    };

    const prepareToParseReceivedHeaders = function(receivedHeaders){
        information.receivedHeaders = receivedHeaders;

        parseNextReceivedHeader();
    };

    const parseNextReceivedHeader = function(){
        var currentHeader   = information.receivedHeaders.shift();

        if(typeof currentHeader == 'undefined'){
            // Out of headers!
            dispRouteAfterDNS(null);
            return;
        }

        if(currentHeader.indexOf('from ') != 0){
            parseNextReceivedHeader();
            return;
        }
        currentHeader = currentHeader.substring(5);
        currentHeader = currentHeader.split(/\sby\s/)[0];
        currentHeader = currentHeader.split('(');
        var domain = currentHeader[0].CLtrim();
        var tcpInfo = new Array();

        for(var j = 1; j < currentHeader.length; j++){
            tcpInfo[j-1] = currentHeader[j].split(')')[0];
        }

        var tcpDomain = new Array();
        var tcpIP = new Array();

        for(var j = 0; j < tcpInfo.length; j++){
            var currentTCP = tcpInfo[j].CLtrim().split('[');

            if(currentTCP[0].indexOf('EHLO') == 0 || currentTCP[0].indexOf('HELO') == 0){
                currentTCP[0] = currentTCP[0].substring(5);
            }

            if(currentTCP[0]){
                tcpDomain[tcpDomain.length] = currentTCP[0].CLtrim();
            }

            if(currentTCP[1]){
                tcpIP[tcpIP.length] = currentTCP[1].split(']')[0].CLtrim();
            }
        }

        for(var j = 0; j < tcpIP.length; j++){
            if(tcpIP[j].CLisIP() && !isPrivate(tcpIP[j])){
                dispRouteAfterDNS(tcpIP[j]);
                return;
            }
        }

        information.tcpDomain = tcpDomain;
        doDNSLookups();
    };

    const doDNSLookups = function(){
        var currentTCPDomain = information.tcpDomain.shift();

        if(typeof currentTCPDomain == 'undefined'){
            // Out of domains!
      if(information.nntp){
        dispRouteAfterDNS(null);
      } else {
        parseNextReceivedHeader();
      }
            return;
        }

        information.currentTCPDomain = currentTCPDomain;
        doCurrentDNSLookup();
    };

    const doCurrentDNSLookup = function(){
        var currentTCPDomain = information.currentTCPDomain;

        if(currentTCPDomain.CLisIP()){
            if(!isPrivate(currentTCPDomain)){
                dispRouteAfterDNS(currentTCPDomain);
                return;
            } else { // If it is a private IP we don't want to do a DNS lookup
                doDNSLookups();
                return;
            }
        }

        if(currentTCPDomain.indexOf('.') != -1){ // If there is a dot left in the domain
            try{
                const dnsService = Components.classes["@mozilla.org/network/dns-service;1"].getService(Components.interfaces.nsIDNSService);
                const eqService  = Components.classes["@mozilla.org/event-queue-service;1"].getService(Components.interfaces.nsIEventQueueService);
                const uiThread   = eqService.getSpecialEventQueue(eqService.UI_THREAD_EVENT_QUEUE);
                currentDNSRequest = dnsService.asyncResolve(currentTCPDomain, false, dnsCallback, uiThread);
                //alert("start DNS lookup -> " + currentTCPDomain);
                return;
            } catch(e){}
        }

        // If there is no dot left in the domain or if the domain crashed the dns
        doDNSLookups();
    };

    const dnsCallback = new function(){
        this.onLookupComplete = function(request, record, status){
            currentDNSRequest = null;

            if(record != null && record.hasMore()){
                var IP = record.getNextAddrAsString();
                if(!isPrivate(IP)){ // You never know
                    dispRouteAfterDNS(IP);
                    return;
                }
            } // This is a double else
            var currentTCPDomain = information.currentTCPDomain.split(".");
            currentTCPDomain.shift();
            information.currentTCPDomain = currentTCPDomain.join(".");

            doCurrentDNSLookup();
        };
    };

    const dispRouteAfterDNS = function(originIP){
    if(originIP == null){
        document.getElementById("senderAddr").value = "NotFound";
    } else {
            //alert(originIP);
            /* set IP */
            document.getElementById("senderIP").value = originIP;
            //document.getElementById("senderAddr").value = "Searching ...";

            /* look up for addr */
            var ipStrValue = inet_aton(originIP);
            var ipIndex = lookUpIndex(ipStrValue, 0, lookUpArray.length);

            /* verify it lies in the IP range */
            /* each entry is divided using more than 2 spaces */
            var curString = lookUpArray[ipIndex];
            //alert(curString);
            //alert("current IP is -> " + originIP);
            var curIPStart = curString.substring(0,
                             curString.indexOf(" "));
            /* leading space regular expression */
            var ls = /^\s*/;
            /* tailing space regular expression */
            var ts = /\s*$/;
            curString = curString.substring(curString.indexOf(" ")).replace(ls, "");
            var curIPEnd = curString.substring(0,
                             curString.indexOf(" "));
            //alert(curIPStart);
            //alert(curIPEnd);
            if (inet_aton(curIPStart) <= ipStrValue &&
                inet_aton(curIPEnd) >= ipStrValue) {
                    curString = curString.substring(curString.indexOf(" ")).replace(ts, "");
                    document.getElementById("senderAddr").value = curString;
                } else {
                    document.getElementById("senderAddr").value = "NotFound";
                }
    }

    };

    const lookUpIndex = function(ipStrValue, low, high)
    {
        while( low < high) {
            var mid = Math.round((high + low) / 2);
            var midIPStr = lookUpArray[mid].substring(0,
                             lookUpArray[mid].indexOf(" "));
            var midValue = inet_aton(midIPStr);
            if(ipStrValue == midValue) {
                //alert("return mid-> " + mid);
                return mid;
            } else if (ipStrValue < midValue) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        //alert("low -> " + low);
        //alert("high -> " + high);
        var lowIPStr = lookUpArray[low].substring(0,
                       lookUpArray[low].indexOf(" "));
        var lowValue = inet_aton(lowIPStr);
        var highIPStr = lookUpArray[high].substring(0,
                       lookUpArray[high].indexOf(" "));
        var highValue = inet_aton(highIPStr);
        if(ipStrValue < highValue)
            return high - 1;
        else if(ipStrValue < lowValue)
            return high;
        else
            return low;
    };

    const loadLookupDatabase = function()
    {
        var req = new XMLHttpRequest();
        req.open('GET', 'chrome://showip/content/qqwry.dat', true);

        req.onreadystatechange = function(aEvt){
            if(req.readyState == 4){
                lookUpArray = req.responseText.split('\r\n');
                //var len = lookUpArray.length;
                //alert(len);
            }
        };
        req.send(null);
    };

    /**
    *   initialization
    */
    const setUp = function()
    {
        /* load ip data */
        loadLookupDatabase();
    };

    const noop = function()
    {
    };

    /**
    *   start of the component
    */
    this.init = function()
    {
        //Initialization
        setUp();

        var listener = {};
        listener.onStartHeaders = noop;
        listener.onEndHeaders   = findIPInfo;
        gMessageListeners.push(listener);
    };
} // const countryLookup = new function()

addEventListener('messagepane-loaded', showIP.init, true);
